package com.fortune.cat.manager;

import com.fortune.cat.ApplicationContextUtils;
import com.fortune.cat.Exception.RouteLoadException;
import com.fortune.cat.bean.RequestRateLimiterConfig;
import com.fortune.cat.bean.RouteDefinitionRecipient;
import com.fortune.cat.constant.ZkPathConstants;
import com.fortune.cat.convert.RouteDefinitionConvert;
import com.fortune.cat.route.VariableRouteDefinitionLocator;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.fortune.cat.enums.RateLimiterEnums.LOCAL_RATE_LIMITER;
import static com.fortune.cat.filter.factory.DefaultRequestRateLimiterFilterFactory.DEFAULT_RATE_LIMITER_FILTER_NAME;

/**
 * @author zhaozeyang
 * @date 2019/4/17 10:34
 * @description 本类主要用来处理 路由信息的加载和更新
 */
@Slf4j
public class DynamicRouteDefinitionManager implements VariableRouteDefinitionLocator, ApplicationEventPublisherAware, InitializingBean {

    private ApplicationEventPublisher publisher;

    private final ZkClient zkClient;

    private final Gson gson = new Gson();

    private final ClassManager classManager;

    private final GatewayFilterManager gatewayFilterManager;

    private final Map<String, RouteDefinition> ROUTE_MAP = Maps.newConcurrentMap();

    public DynamicRouteDefinitionManager(ZkClient zkClient, ClassManager classManager, GatewayFilterManager gatewayFilterManager) {
        this.zkClient = zkClient;
        this.classManager = classManager;
        this.gatewayFilterManager = gatewayFilterManager;
    }

    /**
     * 初始化后加载路由信息到ROUTE_MAP
     */
    @Override
    public void afterPropertiesSet() {
        loadRouteDefinition();
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(ROUTE_MAP.values());
    }

    /**
     * 加载路由信息
     */
    private void loadRouteDefinition() {
        final String routeParent = ZkPathConstants.ROUTE_DEFINITION;
        //判断跟节点如果不存在就创建
        if (!zkClient.exists(routeParent)) {
            zkClient.createPersistent(routeParent, true);
        }
        //获取字节点，每个节点对应一个路由信息
        loadRoute(routeParent);
        zkClient.subscribeDataChanges(routeParent, new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) {
                List<String> expiredExternalClass = loadRoute(dataPath);
                //发布更新事件
                DynamicRouteDefinitionManager.this.publisher.publishEvent(new RefreshRoutesEvent(this));
                //将失效的groovy的filter从ios中清除
                removeBeans(expiredExternalClass);
            }

            @Override
            public void handleDataDeleted(String dataPath) {
                throw new RuntimeException("what happened !!!");
            }
        });

    }

    private List<String> loadRoute(String routeParent) {
        //获取zkROUTE_DEFINITION节点下子节点，即所有路由信息存放的节点
        List<String> routeZks = zkClient.getChildren(routeParent);
        //获取路由信息
        List<RouteDefinitionRecipient> routeDefinitionRecipientList = resolveRouteDefinitionRecipient(routeZks);
        //加载更新filter
        List<GatewayFilterManager.ExternalGatewayFilterFactoryInfo> filterInfolist = reloadFilters(routeDefinitionRecipientList);
        //对象转化RouteDefinition
        List<RouteDefinition> routeDefinitionList = RouteDefinitionConvert.convertToRouteDefinition(routeDefinitionRecipientList);
        //清除旧数据,更换新数据
        ROUTE_MAP.clear();
        //清楚无用的bean
        List<String> expiredExternalClass = gatewayFilterManager.clearExternalFilterFactories();
        //设置可用的filter到filterManager
        gatewayFilterManager.addFilterFactories(filterInfolist);
        //将路由信息放入map缓存
        routeDefinitionList.forEach(routeDefinition -> ROUTE_MAP.put(routeDefinition.getId(), routeDefinition));
        return expiredExternalClass;
    }

    /**
     * 加载filter信息
     *
     * @param collect
     */
    private List<GatewayFilterManager.ExternalGatewayFilterFactoryInfo> reloadFilters(List<RouteDefinitionRecipient> collect) {
        List<GatewayFilterManager.ExternalGatewayFilterFactoryInfo> result = new ArrayList<>();
        collect.forEach(routeDefinitionRecipient -> routeDefinitionRecipient.getFilters().forEach(filter -> {
            if (filter.isExternal()) {
                try {
                    String beanName = classManager.loadExternalBean(new URL(filter.getUrl()), filter.getExternalName());
                    if (beanName == null) {
                        log.error("loadExternalFilter | error filter is {}", filter);
                        throw new RouteLoadException("load external bean fail");
                    }
                    result.add(new GatewayFilterManager.ExternalGatewayFilterFactoryInfo(beanName, (GatewayFilterFactory) ApplicationContextUtils.applicationContext.getBean(beanName)));
                } catch (IOException e) {
                    log.error("loadExternalFilter | error filter is {}", filter, e);
                }
            }
        }));
        return result;
    }

    /**
     * 获取路由信息
     *
     * @param routeZks
     * @return
     */
    private List<RouteDefinitionRecipient> resolveRouteDefinitionRecipient(List<String> routeZks) {
        List<RouteDefinitionRecipient> result = Lists.newArrayList();
        routeZks.forEach(routeZk -> {
            //判断route目录是否存在，没有则创建
            String routePath = ZkPathConstants.buildRoutePath(routeZk);
            if (!zkClient.exists(routePath)) {
                zkClient.createPersistent(routePath, true);
            }
            RouteDefinitionRecipient routeDefinitionRecipient = gson.fromJson(zkClient.readData(routePath).toString(), RouteDefinitionRecipient.class);
            result.add(routeDefinitionRecipient);
        });
        return result;
    }

    private void removeBeans(List<String> expiredExternalClass) {
        expiredExternalClass.forEach(classManager::removeBean);
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}
